/**
 ********************************************************************
 * @author roocket
 * @file    sound.c
 * @version V0.0.0
 * @date    2024/4/26
 * @brief   This file is to indicate sound functions. 
 * @attention Code file rules:
 * rule: file encoding use UTF8;
 * rule: max line length 120 characters;
 * rule: line separator \r\n;
 * rule: use clion auto code format tool.
 */
#include "sound.h"

static int _sound_alsa_set_hw(struct sound_alsa_player *player)
{
    int res;
    int dir = 0;

    res = snd_pcm_hw_params_malloc(&player->pcm_hw_params);
    if (-1 == res) {
        perror("snd pcm hw params malloc error");
        goto pcm_dev;
    }
    printf("set parameters for hardware well......\n");

    res = snd_pcm_hw_params_any(player->pcm_hdl, player->pcm_hw_params);
    if (-1 == res) {
        perror("snd any");
        goto pcm_dev;
    }

    res = snd_pcm_hw_params_set_access(player->pcm_hdl, player->pcm_hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (-1 == res) {
        perror("snd pcm hw params set access error");
        goto pcm_dev;
    }

    /* set format for sound */
    res = snd_pcm_hw_params_set_format(player->pcm_hdl, player->pcm_hw_params, player->snd_audio_format);
    if (-1 == res) {
        perror("snd pcm hw params set format error");
        goto pcm_dev;
    }

    /* set audio channel */
    res = snd_pcm_hw_params_set_channels(player->pcm_hdl, player->pcm_hw_params, player->snd_audio_channel);
    if (-1 == res) {
        perror("snd pcm hw params set channels error");
        goto pcm_dev;
    }

    /* set sample rate */
    res = snd_pcm_hw_params_set_rate_near(player->pcm_hdl, player->pcm_hw_params, &player->snd_rate, &dir);
    if (-1 == res) {
        perror("snd pcm hw params set rate near error");
        goto pcm_dev;
    }

    res = snd_pcm_hw_params_set_buffer_size_near(player->pcm_hdl, player->pcm_hw_params, &player->bsize_frames);
    if (-1 == res) {
        perror("snd pcm hw params set size near error");
        goto pcm_dev;
    }

    res = snd_pcm_hw_params_set_period_size_near(player->pcm_hdl, player->pcm_hw_params, &player->psize_frames, 0);
    if (-1 == res) {
        perror("snd pcm hw params set period size near error");
        goto pcm_dev;
    }

    /* set hardware with options or settings */
    res = snd_pcm_hw_params(player->pcm_hdl, player->pcm_hw_params);
    if (-1 == res) {
        perror("snd pcm hw params error");
        goto pcm_dev;
    }
    printf("set hw params for sound\n");
    return 0;

pcm_dev:
    return -1;
}

static int _sound_alsa_get_hw(struct sound_alsa_player *player)
{
    int res;
    int dir = 0;

    res = snd_pcm_hw_params_get_period_size_min(player->pcm_hw_params, &player->psize_frames, &dir);
    if (-1 == res) {
        perror("snd_pcm_hw_params_get_period_size_min");
        goto pcm_dev;
    }

    res = snd_pcm_hw_params_get_period_size(player->pcm_hw_params, &player->psize_frames, &dir);
    if (-1 == res) {
        perror("snd_pcm_hw_params_get_period_size_min");
        goto pcm_dev;
    }

    res = snd_pcm_hw_params_get_buffer_size_min(player->pcm_hw_params, &player->bsize_frames);
    if (-1 == res) {
        perror("snd_pcm_hw_params_get_period_size_min");
        goto pcm_dev;
    }

    res = snd_pcm_hw_params_get_buffer_size(player->pcm_hw_params, &player->bsize_frames);
    if (-1 == res) {
        perror("snd_pcm_hw_params_get_buffer_size");
        goto pcm_dev;
    }

    return 0;

pcm_dev:
    return -1;
}
static int _sound_alsa_set_sw(struct sound_alsa_player *player)
{
    int res;

    /* set software */
    snd_pcm_sw_params_alloca(&player->pcm_sw_params);

    res = snd_pcm_sw_params_current(player->pcm_hdl, player->pcm_sw_params);
    if (-1 == res) {
        perror("snd pcm sw params current error");
        goto pcm_dev;
    }

    res = snd_pcm_sw_params_set_avail_min(player->pcm_hdl, player->pcm_sw_params, player->psize_frames);
    if (-1 == res) {
        perror("snd pcm sw params set avail min error");
        goto pcm_dev;
    }

    res = snd_pcm_sw_params_set_start_threshold(player->pcm_hdl, player->pcm_sw_params, player->bsize_frames);
    if (-1 == res) {
        perror("snd pcm sw params set start threshold error");
        goto pcm_dev;
    }

    res = snd_pcm_sw_params_set_stop_threshold(player->pcm_hdl, player->pcm_sw_params, player->bsize_frames);
    if (-1 == res) {
        perror("snd pcm sw params set stop threshold error");
        goto pcm_dev;
    }

    res = snd_pcm_sw_params(player->pcm_hdl, player->pcm_sw_params);
    if (-1 == res) {
        perror("snd pcm sw params error");
        goto pcm_dev;
    }

    snd_pcm_format_physical_width(player->snd_audio_format);
    snd_pcm_format_width(player->snd_audio_format);

    return 0;
        
pcm_dev:
    return -1;
}

static int _sound_alsa_get_sw(struct sound_alsa_player *player)
{
    return 0;
}

/* init the player for sound */
int sound_alsa_player_init(const char *audio_device, struct sound_alsa_player *player)
{
    int res = -1;
    int dir = 0;

    res = snd_pcm_open(&player->pcm_hdl, audio_device, player->snd_pcm_stream_method, 0);
    if (-1 == res) {
        perror("snd pcm open error");
        return -1;
    }
    printf("audio device opened well......\n");

    res = _sound_alsa_set_hw(player);
    if (-1 == res) {
        perror("_sound alsa set hw error");
        goto pcm_dev;
    }

    res = _sound_alsa_get_hw(player);
    if (-1 == res) {
        perror("_sound alsa get hw error");
        goto pcm_dev;
    }
    
    res = _sound_alsa_set_sw(player);
    if (-1 == res) {
        perror("_sound alsa set sw error");
        goto pcm_dev;
    }

    res = _sound_alsa_get_sw(player);
    if (-1 == res) {
        perror("_sound alsa get sw error");
        goto pcm_dev;
    }

    printf("be ready to playback audio data......\n");

    return 0;

pcm_dev:
    snd_pcm_close(player->pcm_hdl);
    return -1;
}

/* deinit the player for sound */
void sound_alsa_player_deinit(struct sound_alsa_player *player)
{
    snd_pcm_close(player->pcm_hdl);
    return;
}

/* capture audio data */
int sound_alsa_capture_audio_data(struct sound_alsa_player *capture_player, int (*audio_cap_callback)(const char *buffer, int len))
{
    int res = -1;

    res = snd_pcm_readi(capture_player->pcm_hdl, capture_player->audio_buffer, capture_player->buffer_frame_cnt);
    if (res != capture_player->buffer_frame_cnt) {
        perror("snd pcm readi error");
        return -1;
    }

    /* @todo for users with its callback function */
    audio_cap_callback(capture_player->audio_buffer, capture_player->snd_audio_channel * capture_player->buffer_frame_cnt);
    return 0;
}

/* playback audio data */
int sound_alsa_playback_audio_data(struct sound_alsa_player *playback_player, const char *buffer)
{
    int err;
    
    while(err = snd_pcm_writei(playback_player->pcm_hdl, buffer, playback_player->psize_frames)<0)
    {
        if (err == -EAGAIN)
        {
            printf("again");
            snd_pcm_wait(playback_player->pcm_hdl,1000);
        }
        else if (err == -EPIPE)
        {
            snd_pcm_prepare(playback_player->pcm_hdl);
            printf("buffer underrun!!!!\n");
        }
        else if (err == -ESTRPIPE)
        {
            printf("Need suspend!\n");
        }
        else if (err < 0)
        {
            printf("snd_pcm_writei error:%s\n",snd_strerror(err));
        }
    }
    return 0;
}

int sound_alsa_get_playback_volume(const char *audio_device, const char *which_element, struct mixer_info *mixer_info)
{
    int res;
    snd_mixer_elem_t *mixer_elem = NULL;
    snd_mixer_t *mixer = NULL;

    res =snd_mixer_open(&mixer, 0);
    if (-1 == res) {
        perror("snd mixer open error");
        return -1;
    }

    snd_mixer_attach(mixer, audio_device);
    snd_mixer_selem_register(mixer, NULL, NULL);
    snd_mixer_load(mixer);

    /* find the element */
    mixer_elem = snd_mixer_first_elem(mixer);
    while (mixer_elem) {
        printf("the element of list %s\n", snd_mixer_selem_get_name(mixer_elem));

        if (!strcmp(which_element, snd_mixer_selem_get_name(mixer_elem))) {
            printf("element name find %s\n", snd_mixer_selem_get_name(mixer_elem));
            break;
        }
        mixer_elem = snd_mixer_elem_next(mixer_elem);
    }

    if (!mixer_elem) {
        perror("mixer element error");
        goto out;
    }

    snd_mixer_selem_get_playback_volume_range(mixer_elem, &mixer_info->vol_min, &mixer_info->vol_max);
    printf("the range of volume %ld - %ld\n", mixer_info->vol_min, mixer_info->vol_max);
    snd_mixer_handle_events(mixer);
    snd_mixer_selem_get_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_LEFT, &mixer_info->left_val);
    snd_mixer_selem_get_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_RIGHT, &mixer_info->right_val);
    printf("currnet volume: left vol = %ld, right vol = %ld\n", mixer_info->left_val, mixer_info->right_val);

    snd_mixer_close(mixer);
    return 0;
out:
    snd_mixer_close(mixer);
    return -1;
}

int sound_alsa_set_playback_volume(const char *audio_device, const char *which_element, struct mixer_info *mixer_info)
{
    int res;
    snd_mixer_elem_t *mixer_elem = NULL;
    snd_mixer_t *mixer = NULL;

    res =snd_mixer_open(&mixer, 0);
    if (-1 == res) {
        perror("snd mixer open error");
        return -1;
    }

    snd_mixer_attach(mixer, audio_device);
    snd_mixer_selem_register(mixer, NULL, NULL);
    snd_mixer_load(mixer);

    /* find the element */
    mixer_elem = snd_mixer_first_elem(mixer);
    while (mixer_elem) {
        printf("the element of list %s\n", snd_mixer_selem_get_name(mixer_elem));

        if (!strcmp(which_element, snd_mixer_selem_get_name(mixer_elem))) {
            printf("element name find %s\n", snd_mixer_selem_get_name(mixer_elem));
            break;
        }
        mixer_elem = snd_mixer_elem_next(mixer_elem);
    }

    if (!mixer_elem) {
        perror("mixer element error");
        goto out;
    }

    snd_mixer_selem_get_playback_volume_range(mixer_elem, &mixer_info->vol_min, &mixer_info->vol_max);
    printf("the range of volume %ld - %ld\n", mixer_info->vol_min, mixer_info->vol_max);

    /* validate the setting for volume */
    if (mixer_info->left_val < mixer_info->vol_min || mixer_info->left_val > mixer_info->vol_max) {
        perror("left val error setting, so mute!");
        mixer_info->left_val = 0;
    }

    if (mixer_info->right_val < mixer_info->vol_min || mixer_info->right_val > mixer_info->vol_max) {
        perror("right val error setting, so mute!");
        mixer_info->right_val = 0;
    }

    snd_mixer_handle_events(mixer);
    snd_mixer_selem_set_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_LEFT, mixer_info->left_val);
    snd_mixer_selem_set_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_RIGHT, mixer_info->right_val);
    printf("currnet volume: left vol = %ld, right vol = %ld\n", mixer_info->left_val, mixer_info->right_val);

    snd_mixer_close(mixer);
    return 0;
out:
    snd_mixer_close(mixer);
    return -1;
}

